import problem
import problem_agent
from search.algorithm import *
from search.astar import *
from osm_utils4 import DEFAULT_CAR, CAR_PETROL_PROFILE
import osm_utils4


class SolutionProblemState(problem.ProblemState):
    '''
    each state holds the junction key and the target junction key. In addition it holds a reference to the map for enabling 
    all requested map calculations and the relevant car profile.
    The different rates holds the weight we give for each heuristic.
    '''
    def __init__(self,currentJunctionKey,targetJunctionKey,roadMap,distRate,speedRate,efficRate,carProfile = DEFAULT_CAR, isCombined = False):
        self.currentJunctionKey = currentJunctionKey
        self.targetJunctionKey = targetJunctionKey
        self.roadMap = roadMap
        self.carProfile = carProfile
        self.distRate = distRate
        self.speedRate = speedRate
        self.efficRate = efficRate
        self.isCombined = isCombined
        
    def getSuccessors(self):
        '''
        when combining different heuristics we bring the different values to have same proportion in the solution
        '''
        if self.isCombined:
            eqDist = 1
            eqTime = 10
            eqPetrol = 100
        else:
            eqDist = 1
            eqTime = 1
            eqPetrol = 1           
            
        successors = {}
        tempJunction = self.roadMap.GetJunction(self.currentJunctionKey)
        for link in tempJunction.links:
            distance = self.shortestCost(link)
            cost = eqDist*self.distRate*distance+eqTime*self.speedRate*self.fastestCost(link)+eqPetrol*self.efficRate*self.efficiencyCost(link)
            successors.update({SolutionProblemAction(cost,distance,link.speed) : SolutionProblemState(link.target,self.targetJunctionKey,self.roadMap,self.distRate,self.speedRate,self.efficRate,self.carProfile)})
        return successors
    
    def shortestCost(self, link):
    #    a cost method that gets a link and returns the cost according to the distance in km
        return link.distance/1000.0
    
    def fastestCost(self, link):
    #    a cost method that gets a link and returns the cost according to the travel time in hours
        if self.isCombined:
            eqTime = 10
        else:
            eqTime = 1
        distance = link.distance/1000.0
        return eqTime*distance/link.speed
    
    def efficiencyCost(self, link):
    #    a cost method that gets a link and returns the cost according to the gas efficiency in litters
        if self.isCombined:
            eqPetrol = 100
        else:
            eqPetrol = 1
        distance = link.distance/1000.0
        return eqPetrol*distance / CAR_PETROL_PROFILE[self.carProfile][min(link.speed,120)]
    
    def isGoal(self):
        return self.currentJunctionKey ==  self.targetJunctionKey
    
    def __cmp__(self, other):
        return cmp(self.currentJunctionKey,other.currentJunctionKey)
    
    def __hash__(self):
        return hash((self.currentJunctionKey,self.targetJunctionKey))
    
    def __str__(self):
        tempJunction = self.roadMap.GetJunction(self.currentJunctionKey)
        return "<State name=%s lat=%s lon=%s>" % (self.currentJunctionKey, tempJunction.lat, tempJunction.lon)
    
    
class SolutionProblemAction(problem.ProblemAction):
    '''
    each action is a link, defined by it's distance and speed allowed
    '''
    def __init__(self,cost,dist,speed):
        self.dist = dist
        self.speed = speed
        problem.ProblemAction.__init__(self, cost)  
        
    def __cmp__(self, other):
        return cmp(self.cost,other.cost)
    
    def __hash__(self):
        return hash(id(self))
    
    def __str__(self):
        return "<Action cost %s>" % (self.cost)
    
class WayzProblemAgent (problem_agent.ProblemAgent):
    '''
    agent for section A, uses CompositeHeuristic and normal Astar
    '''
    def solve(self, problem_state, time_limit = problem_agent.NO_LIMIT):
        aStarSearch = AStar(time_limit)
        myHeuristic = CompositeHeuristic(problem_state.carProfile)
        return aStarSearch.find(problem_state,myHeuristic)
    
class WayzWeightedProblemAgent (problem_agent.ProblemAgent):
    '''
    agent for section A weighted problem, uses CompositeHeuristic and normal WeightedAStar
    '''
    def solve(self, problem_state, weight,time_limit = problem_agent.NO_LIMIT):
        aStarSearch = WeightedAStar(weight,time_limit)
        myHeuristic = CompositeHeuristic(problem_state.carProfile)
        return aStarSearch.find(problem_state,myHeuristic)
    

class ShortestWayProblemAgent (problem_agent.ProblemAgent):
    def solve(self, problem_state, time_limit = problem_agent.NO_LIMIT):
        aStarSearch = AStar(time_limit)
        myHeuristic = ShortestWayHeuristic()
        return aStarSearch.find(problem_state,myHeuristic)
    
class FastestWayProblemAgent (problem_agent.ProblemAgent):
    def solve(self, problem_state, time_limit = problem_agent.NO_LIMIT):
        aStarSearch = AStar(time_limit)
        myHeuristic = FastestWayHeuristic()
        return aStarSearch.find(problem_state,myHeuristic)

class CompositeHeuristic(Heuristic):
    '''
    our main heuristic.
    return the evaluation of the heuristic according to the given heuristic weights
    '''
    def __init__(self,carProfile):
        self.maxConsumption = max(CAR_PETROL_PROFILE[carProfile])
        
    def evaluate(self, problem_state):
        rates = [problem_state.distRate,problem_state.speedRate,problem_state.efficRate]
        heuristic = []        
        dist = problem_state.roadMap.JunctionDistance(problem_state.currentJunctionKey , problem_state.targetJunctionKey) 
        heuristic.append(dist)
        heuristic.append(dist/120.0)
        heuristic.append(dist/float(self.maxConsumption))
        evaluation = 0
        for i in range(3):
            evaluation += rates[i]*heuristic[i]
        return (evaluation)    
 
class ShortestWayHeuristic(Heuristic):
    def evaluate(self, problem_state):
        aerialDistance = problem_state.roadMap.JunctionDistance(problem_state.currentJunctionKey , problem_state.targetJunctionKey)        
        return (aerialDistance)
    
class FastestWayHeuristic(Heuristic):
    def evaluate(self, problem_state):
        aerialDistance = problem_state.roadMap.JunctionDistance(problem_state.currentJunctionKey , problem_state.targetJunctionKey)        
        return (aerialDistance/120.0)
    
class EfficientWayHeuristic(Heuristic):
    def __init__(self,carProfile):
        self.maxConsumption = CAR_PETROL_PROFILE[carProfile][1]
    
    def evaluate(self, problem_state):
        aerialDistance = problem_state.roadMap.JunctionDistance(problem_state.currentJunctionKey , problem_state.targetJunctionKey)        
        return (aerialDistance*self.maxConsumption)


class WeightedAStar (AStar):

    def __init__(self,heuristicWeight, max_depth=infinity):
       
        self.heuristicWeight = heuristicWeight
        AStar.__init__(self, max_depth)

    def find(self, problem_state, heuristic):
        '''
        A* search is best-first graph search with f(n) = g(n)+h(n).
        You need to specify the h function when you call astar_search.
        Uses the pathmax trick: f(n) = max(f(n), g(n)+h(n)).

        @param problem_state: The initial state to start the search from.
        @param heuristic: A heuristic function that scores the Problem states.
        '''
        # This is the node evaluation function for the given heuristic.
        def evaluator(node):
            return (1-self.heuristicWeight)*node.path_cost + self.heuristicWeight*heuristic.evaluate(node.state)
    
        # This is a generator for the PriorityQueue we need.
        def queue_generator():
            return PriorityQueue(evaluator)

        # Use a graph search with a minimum priority queue to conduct the search.
        search = GraphSearch(queue_generator, self.max_depth)
        return search.find(problem_state)    